package cn.ly.rabbit.controller;

import cn.ly.rabbit.config.RabbitConfig;
import cn.ly.rabbit.model.User;
import lombok.extern.log4j.Log4j2;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;


/**
 * 1.更细粒度的失败通知、发送者确认
 * 经过测试，发现配好全局的失败通知后，就不能使用该细粒度的失败回调了。二者只能取其一
 * 故：要么配全局，要么都配细粒度的
 *
 * 2.测试Direct、Topic、死信交换器
 */
@Log4j2
@RestController
public class HelloSenderController implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    //此为全局配置的RabbitTemplate
    @Autowired  //如果使用细粒度的配置，需要把这个@Autowired注释了，然后将下面的构造器解封。还得把全局配置的@Bean RabbitMQ配置也给删掉
    private RabbitTemplate rabbitTemplate;

//    /**
//     * 构造注入RabbitMqTemplate
//     * 主要是为了给 该业务添加单独的回调方法：即【细粒度的失败通知】
//     */
//    @Autowired
//    public HelloSenderController(RabbitTemplate rabbitTemplate){
//        //开启失败通知，其实2.2版本amqp默认就开了
//        rabbitTemplate.setMandatory(true);
//        //失败通知的回调
//        rabbitTemplate.setReturnCallback(this);
//        //发送方确认收到的回调
//        rabbitTemplate.setConfirmCallback(this);
//        this.rabbitTemplate = rabbitTemplate;
//    }

    //简单队列
    @GetMapping("/send1")
    public String send(String msg) {
        msg += new Date();
        System.out.println(">>>>>>>>>>begin to send msg:" + msg);
        rabbitTemplate.convertAndSend(RabbitConfig.Simple_Queue_Name, msg);
        return msg;
    }

    //发送到：Direct交换器上，路由键为：sb.ll.email
    @GetMapping("/send2")
    public String send2(String msg, String routeKey) {
        if (routeKey == null || routeKey.equals("")) {
            routeKey = "sb.ll.email";
        }
        msg += new Date();
        System.out.println(RabbitConfig.Direct_Exchange_Name + ">>>>>>>>>>begin to send Direct:" + msg);
        rabbitTemplate.convertAndSend(RabbitConfig.Direct_Exchange_Name, routeKey, msg);
        return RabbitConfig.Direct_Exchange_Name + " will send:" + msg;
    }

    //发送到：Topic交换器上，路由键为：li.123.user
    @GetMapping("/send3")
    public String send3(String name, String routeKey) {
        if (routeKey == null || routeKey.equals("")) {
            routeKey = "li.123.user";
        }
        if (name == null) {
            name = "lies";
        }
        User user = new User(name, 25);
        System.out.println(RabbitConfig.Topic_Exchange_Name + ">>>>>>>>>>begin to send User:" + user.getName());
        rabbitTemplate.convertAndSend(RabbitConfig.Topic_Exchange_Name, routeKey, user);
        return RabbitConfig.Topic_Exchange_Name + " will send user:" + name;
    }


    /**
     * 【死信交换器的使用，情况一：】
     * 发送到：Topic交换器上，路由键为：test.b.123
     * 由该路由test.b.123，其将被转发到一个设置了【死信属性】的B_Queue队列中（我在消费者中定义的）
     * 如果该消息，被拒绝且被抛弃不再回到队列。那么其将成为死信消息，由B_Queue自动转发到死信交换器上（消费者定义，生产者这儿啥也没配置）
     */
    @GetMapping("/send4")
    public String send4(String msg, String routeKey) {
        if (routeKey == null || routeKey.equals("")) {
            routeKey = "test.b.123";
        }
        msg += new Date();
        System.out.println(RabbitConfig.Topic_Exchange_Name + ">>>>>>>>>>begin to send TOPIC:" + msg);

//        1.消息被消费者抛弃，未返回队列（requeue=false)。则转发到死信交换器
        rabbitTemplate.convertAndSend(RabbitConfig.Topic_Exchange_Name, routeKey, msg, message -> {
            // 如果配置了 params.put("x-message-ttl", 5 * 1000); 那么这一句也可以省略,具体根据业务需要是声明 Queue 的时候就指定好延迟时间还是在发送自己控制时间
            message.getMessageProperties().setExpiration(1000 * 10 + "");   //10s过期时间
            return message;
        });

//        //死信交换器的使用，情况二：延时订单，可见 DelayOrderController类中
//        //2.投递消息到队列后，消息过期（即我们没有设置监听器去消费），然后被转发到死信路由器
//        rabbitTemplate.convertAndSend(RabbitConfig.DLX_Delay_Exchange_Name, RabbitConfig.Order_Queue_Name, msg, processor -> {
//            // 如果配置了 params.put("x-message-ttl", 5 * 1000); 那么这一句也可以省略,具体根据业务需要是声明 Queue 的时候就指定好延迟时间还是在发送自己控制时间
//            processor.getMessageProperties().setExpiration(1000 * 8 + "");   //10s过期时间
//            return processor;
//        });
        return RabbitConfig.DLX_Delay_Exchange_Name + " will send:" + msg;

    }




//=============================下面为本类业务的细粒度失败通知~
    /**
     * 发送者确认
     */
    @Override
    public void confirm(@Nullable CorrelationData correlationData, boolean ack, @Nullable String cause) {
        if (ack) {
            System.out.println("HelloSenderController....发送者确认：>>>>>>>>>>消息发送成功");
        } else {
            System.out.println("来自HelloSenderController的....发送者确认：>>>>>>>>>>消息发送失败：maybe rabbitMq has been stopped");
            log.error("消息发送失败:correlationData({}),ack({}),cause({})", correlationData, ack, cause);
        }
    }

    /**
     * 失败通知
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText,
                                String exchange, String routingKey) {
        System.out.println(">>>>>>>来自HelloSenderController的....失败通知");
        log.error("来自HelloSenderController的....失败通知：>>>>>>>>>>>消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}",
                exchange, routingKey, replyCode, replyText, message);
    }


}
